/**
 * elancom Corporation copy right 2012 - 2015  
 *   
 * $Header: https://192.168.1.224/svn/elancom/product/elancom-platform/trunk/src/main/java/com/elancom/framework/commons/json/parser/SymbolTable.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Id: SymbolTable.java 1034 2012-07-02 03:09:12Z lanzhouxue $
 * $Author: lanzhouxue $
 * $Date: 2012-07-02 11:09:12 +0800 (Mon, 02 Jul 2012) $
 * $Revision: 1034 $
 */
package com.elancom.framework.commons.json.parser;

/**
 * @author elancom
 */
public class SymbolTable {

	public static final int DEFAULT_TABLE_SIZE = 128;
	public static final int MAX_BUCKET_LENTH = 8;

	private final Entry[] buckets;
	private final String[] symbols;
	private final char[][] symbols_char;

	private final int indexMask;

	public SymbolTable() {
		this(DEFAULT_TABLE_SIZE);
	}

	public SymbolTable(int tableSize) {
		indexMask = tableSize - 1;
		buckets = new Entry[tableSize];
		symbols = new String[tableSize];
		symbols_char = new char[tableSize][];
	}

	public String addSymbol(char[] buffer, int offset, int len) {
		// search for identical symbol
		final int hash = hash(buffer, offset, len);
		return addSymbol(buffer, offset, len, hash);
	}

	/**
	 * Adds the specified symbol to the symbol table and returns a reference to the unique symbol. If the symbol already exists, the previous symbol reference
	 * is returned instead, in order guarantee that symbol references remain unique.
	 * 
	 * @param buffer
	 *            The buffer containing the new symbol.
	 * @param offset
	 *            The offset into the buffer of the new symbol.
	 * @param len
	 *            The length of the new symbol in the buffer.
	 */
	public String addSymbol(char[] buffer, int offset, int len, int hash) {
		// int bucket = indexFor(hash, tableSize);
		final int bucket = hash & indexMask;

		final String sym = symbols[bucket];

		boolean match = true;

		if (sym != null) {
			if (sym.length() == len) {
				final char[] characters = symbols_char[bucket];

				for (int i = 0; i < len; i++) {
					if (buffer[offset + i] != characters[i]) {
						match = false;
						break;
					}
				}

				if (match) {
					return sym;
				}
			} else {
				match = false;
			}
		}

		{
			int entryIndex = 0;
			for (Entry entry = buckets[bucket]; entry != null; entry = entry.next) {
				final char[] characters = entry.characters;
				if (len == characters.length && hash == entry.hashCode) {
					boolean eq = true;
					for (int i = 0; i < len; i++) {
						if (buffer[offset + i] != characters[i]) {
							eq = false;
							break;
						}
					}

					if (!eq) {
						entryIndex++;
						continue;
					}
					return entry.symbol;
				}
			}
			if (entryIndex >= MAX_BUCKET_LENTH) {
				return new String(buffer, offset, len);
			}
		}

		final Entry entry = new Entry(buffer, offset, len, hash, buckets[bucket]);
		buckets[bucket] = entry; // 并发是处理时会导致缓存丢失，但不影响正确性
		if (match) {
			symbols[bucket] = entry.symbol;
			symbols_char[bucket] = entry.characters;
		}
		return entry.symbol;
	}

	public static final int hash(char[] buffer, int offset, int len) {
		int h = 0;
		int off = offset;

		for (int i = 0; i < len; i++) {
			h = 31 * h + buffer[off++];
		}
		return h;
	}

	protected static final class Entry {

		public final String symbol;
		public final int hashCode;

		public final char[] characters;
		public final byte[] bytes;

		public Entry next;

		/**
		 * Constructs a new entry from the specified symbol information and next entry reference.
		 */
		public Entry(char[] ch, int offset, int length, int hash, Entry next) {
			characters = new char[length];
			System.arraycopy(ch, offset, characters, 0, length);
			symbol = new String(characters).intern();
			this.next = next;
			hashCode = hash;
			bytes = null;
		}

	}

}
